Перейти к основному содержимому

Интеграционные тесты на дедлоки и одновременные запросы

· 2 мин. чтения

Если вы так же озабочены тестированием как и я, то вы возможно сталкивались с проблемами дедлоков при транзакциях. Транзакции в БД дело хорошее, особенно на все REST-запросы, т.к. он становится атомарной операцией. Однако если вы вместе с этим затрагиваете часто используемую таблицу или операция происходит на файлах или с другими сетевыми запросами, то вы можете вызвать дедлок. 

MySQL/InnoDB дедлок возникает из-за двух транзакций пытающихся в разном порядке изменить одни и те же данные. Если вы используете триггеры, то вам в коде даже не надо явно объявлять о транзакции - любой UPDATE с триггером потенциально опасен дедлоком. При этом уровень изоляции транзакции не влияет на вероятность его получения

Детали в базе можно увидеть так:

SHOW ENGINE InnoDB STATUS;

------------------------
LATEST DETECTED DEADLOCK
------------------------
2016-10-21 08:21:41 0x7f772b726700
*** (1) TRANSACTION:
TRANSACTION 9651, ACTIVE 0 sec starting index read
mysql tables in use 3, locked 3
LOCK WAIT 4 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 1052, OS thread handle 140149807552256, query id 128292 localhost 127.0.0.1 tactic updating
UPDATE folder c SET (...)

*** (2) TRANSACTION:
TRANSACTION 9650, ACTIVE 0 sec fetching rows
mysql tables in use 3, locked 3
8 lock struct(s), heap size 1136, 183 row lock(s), undo log entries 1
MySQL thread id 1048, OS thread handle 140149806753536, query id 128291 localhost 127.0.0.1 tactic Sending data
UPDATE folder c SET (...)

Обычно советуют в случае дедлока перезапускать всю операцию, но это может усложнить весь метод. 

Если вы не хотите усложнять себе жизнь обработкой одновременных запросов, можно сделать лок на весь POST-запрос, скажем используя memcache. 

Так или иначе, в итоге вы хотите протестировать что два или более одновременных REST-запроса на один и тот же URL не упадут используя PHPUnit. В php это реализуется методом curl_multi_init:

function TwoConcurrentSaves_ShouldNotCauseError() {
$urls = [
'http://kurapov.ee/file/save',
'http://kurapov.ee/file/save',
];
$node_count = count($urls);

$chs = [];
$master = curl_multi_init();

for ($i = 0; $i < $node_count; $i++) {
$url = $urls[$i];
$ch = curl_init($url);

curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, ['collectionID' => 13, 'quality'=> 90]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_multi_add_handle($master, $ch);
$chs[$i] = $ch;
}

do {
curl_multi_exec($master, $running);
} while ($running > 0);

for ($i = 0; $i < $node_count; $i++) {
$response = curl_multi_getcontent($chs[$i]);

$this->assertNotContains('Serialization failure', $response);
$this->assertNotContains('Deadlock found', $response);
}
}